package com.example.demo.service;

import com.example.demo.common.DbUtil;
import com.example.demo.entity.FileMeta;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: DELL
 * Date: 2023-06-30
 * Time: 15:37
 */

// 通过这个类封装 file_meta 表的操作
@Service
public class FileService {

    // 运行与用户电脑, 需要自动建表
    public void initDB() throws SQLException {
        // 1. 根据 sql.db 中读取语句
        // 2. 根据 sql 语句调用 jdbc 执行
        Connection connection = null;
        Statement statement = null;
        try {
            // 1. 建立连接
            connection = DbUtil.getConnection();
            statement = connection.createStatement();

            // 2. 获取 sql.db 中的雨具
            String[] sqls = getInitSQL();
            for (String sql : sqls) {
                System.out.println("[innitDB] sql : " + sql);
                // 读取 sqls 中的 sql 语句并执行
                statement.executeUpdate(sql);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DbUtil.close(connection, statement, null);
        }
    }

    // 从 sql.db 中读取内容.
    // sql.db 中允许有多个 sql 语句
    private String[] getInitSQL() {

        // 用来存储最终结果
        StringBuilder stringBuilder = new StringBuilder();

        // 1. 确定文件路径, resources 包下的目录为 "Resources Root" - 资源文件根目录
        //    图片, 音频, 视频, 字体, 配置, 图标......
        // InoutStream 字节流, 此处我们的 sql.db 为一个字符流需要转换 // 类对象的类加载器对象并打开某个文件
        try (InputStream inputStream = FileService.class.getClassLoader().getResourceAsStream("db.sql")) {
            // 将 InputStream 字节流转为 InputStreamReader 字符流
            try (InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf8")) {
                while (true) {
                    // 返回值虽然是 int 类型, 但是读取到的 ch 实际上是一个 char 类型的字符
                    int ch = inputStreamReader.read(); // 读取内容, 返回值虽然是一个int 只是为了标记 -1 读取结束状态
                    if (ch == -1) {
                        // 读取结束, 结束循环
                        break;
                    }
                    // 每次读取一个字符并添加
                    stringBuilder.append((char) ch);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 转为数组以后, 按照分号分隔, 一个分号代表一个 SQL 语句执行完毕. 最终将整个文件切分成 SQL 数组
        return stringBuilder.toString().split(";");
    }

    // 插入本地磁盘目录文件到数据库, 批量插入
    public void add(List<FileMeta> fileMetas) throws SQLException {
        // 批量操作可以使用循环来做, 但是太低效. 采取事务的形式来进行批量操作

        Connection connection = null;
        PreparedStatement preparedStatement = null;

        // 1. 将连接中的自动提交功能关闭.
        //    默认情况下, jdbc 中的 Connection 每次执行 execute 系列方法都会和数据库进行一次交互,
        //    这个过程中会针对数据库进行加锁等操作, 为了统一在最开始的时候进行加锁, 统一在最后进行一起解锁, 就需要关闭自动提交
        // jdbc 默认情况下每次执行一个 sql 都是一个事务, 只不过此时的事务中只有一个 sql
        try {
            // 和数据库建立连接并且关闭自动提交事务
            connection = DbUtil.getConnection();
            connection.setAutoCommit(false);

            // 构造 sql 语句
            String sql = "insert into file_meta values(null, ?, ?, ?, ?, ?, ?, ? )";
            preparedStatement = connection.prepareStatement(sql);

            // 替换 sql 占位符
            for (FileMeta fileMeta : fileMetas) {
                // 设置文件名
                preparedStatement.setString(1, fileMeta.getName());
                // 设置文件路径
                preparedStatement.setString(2, fileMeta.getPath());
                // 设置是否为目录
                preparedStatement.setBoolean(3, fileMeta.isDirectory());
                // 设置拼音
                preparedStatement.setString(4, fileMeta.getPinyin());
                // 设置拼音首字母
                preparedStatement.setString(5, fileMeta.getPinyinInFirst());
                // 设置文件大小
                preparedStatement.setLong(6, fileMeta.getSize());
                // 设置修改时间
                preparedStatement.setTimestamp(7, new Timestamp(fileMeta.getLastModify()));

                // 2. 针对每个要执行的 SQL 使用 PreparedStatement 提供的 addBatch 方法来积累
                //    PreparedStatement 中是可以包含多个 sql 的, 这里的每个 sql 为一个 Batch
                preparedStatement.addBatch(); // 把上面构造的 sql 保存起来, 并且可以允许重新构造 sql

                // 3. 使用 executeBatch 统一执行上述所有的 Batch
                preparedStatement.executeBatch(); // 执行之前累积的 Batch

                // 4. 使用 commit 告知数据库执行完毕, 并且进行对数据库上的锁释放
                connection.commit();
            }

        } catch (SQLException e) {
            // 5. 如果上述出现问题, 使用 rollback 进行回滚, 保证原子性
            try {
                // 未连接上则无法回滚
                if (connection != null) {
                    connection.rollback();
                }
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            DbUtil.close(connection, preparedStatement, null);
        }

    }

    // 按照特定关键词 ( 全拼, 首字母, 或者一部分 ) 进行查询
    // 查询的 pattern 有可能是名字, 中文拼音 或者中文拼音首字母的一部分
    public List<FileMeta> searchByPattern(String pattern) throws SQLException {

        List<FileMeta> fileMetas = new ArrayList<>();
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = DbUtil.getConnection();

            // 进行全模糊匹配, 只要包含 pattern 即会被匹配到
            // 构造 sql
            String sql = "select name, path, is_directory, size, last_modify from file_meta"
                    + "where name like ? or pinyin like ? or pinyin_first like ? "
                    + "order by path, name";
            // 预执行
            statement = connection.prepareStatement(sql);

            // 替换占位符
            statement.setString(1, "%" + pattern + "%");
            statement.setString(2, "%" + pattern + "%");
            statement.setString(3, "%" + pattern + "%");
            resultSet = statement.executeQuery(); // 获取查询集合结果
            while (resultSet.next()) {

                // result 中存放的是查询到的符合条件的每一列
                String name = resultSet.getString("name");
                String path = resultSet.getString("path");
                boolean isDirectory = resultSet.getBoolean("is_directory");
                long size = resultSet.getLong("size");
                Timestamp lastModify = resultSet.getTimestamp("last_modify");

                // 构建文件对象
                FileMeta fileMeta = new FileMeta(name, path, isDirectory, size, lastModify.getTime());

                fileMetas.add(fileMeta); // 添加到集合中
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DbUtil.close(connection, statement, resultSet);
        }

        return fileMetas;
    }

    // 给定路径进行扫描, 查询该路径下的所有结果.
    // 后续反复扫描更新数据库时使用
    public List<FileMeta> searchByPath(String targetPath) throws SQLException {

        List<FileMeta> fileMetas = new ArrayList<>();

        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;

        try {
            connection = DbUtil.getConnection();

            // 构造 sql
            String sql = "select name, path, is_directory, size, last_modify from file_meta"
                    + "where path = ?";
            // 预处理
            statement = connection.prepareStatement(sql);
            // 替换占位符
            statement.setString(1, targetPath);
            // 执行查询获取结果集z
            resultSet = statement.executeQuery();

            while (resultSet.next()) {
                String name = resultSet.getString("name");
                String path = resultSet.getString("path");
                boolean isDirectory = resultSet.getBoolean("is_directory");
                long size = resultSet.getLong("size");
                Timestamp lastModify = resultSet.getTimestamp("last_modify");

                FileMeta fileMeta = new FileMeta(name, path, isDirectory, size, lastModify.getTime());

                fileMetas.add(fileMeta);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DbUtil.close(connection, statement, resultSet);
        }

        return fileMetas;
    }

    // 批量删除文件- 用户在本地磁盘上进行了删除操作, 因此需要根据路径来进行更新
    // 删除的可能是一个普通文件直接进行删除
    // 如果是一个目录, 那么需要删除这个目录下的所有普通文件和子目录
    public void delete(List<FileMeta> fileMetas) throws SQLException {
        Connection connection = null;
        PreparedStatement statement = null;


        try {
            connection = DbUtil.getConnection();

            // 1. 关闭自动提交
            connection.setAutoCommit(false);

            // 2. 删除目录或者普通文件
            for (FileMeta fileMeta : fileMetas) {
                String sql = null;
                if (!fileMeta.isDirectory()) {
                    // 不是一个目录
                    sql = "delete from file_meta where name=? and path=? ";
                } else {
                    // 为一个目录
                    // 删除文件本身已经模糊匹配子文件 path : e:/test %, 以 e:/test 开头的都删除
                    sql = "delete from file_meta where (name=? and path=?) or (path like ?)";

                }

                // 使用 addBatch 的前提是 sql 是一个固定的模板
                // 此处使用 ? 进行占位符替换以后, 不一定是一个相同的 sql 模板 ( 即针对文件还是目录不一样 sql )
                // 因此只能使用重新构造 sql

                // 预处理 sql
                statement = connection.prepareStatement(sql);

                // 替换占位符
                if (!fileMeta.isDirectory()) {
                    // 文件情况
                    statement.setString(1, fileMeta.getName());
                    statement.setString(2, fileMeta.getPath());
                } else {

                    // TODO : e:/a/b/... 现在删除的是 b 这个目录, 那么这里的删除子文件夹就会出问题
                    // 此时针对 b 这个目录来说, name: b  path: e:/a
                    // 因此这里要写成 e:/a/b/% 这样才能删除 b 目录下面的子文件
                    // path like e:/a/b/'%'  -> path + '/' + name + '/' + '%' 的 sql 形式

                    // 目录情况
                    statement.setString(1, fileMeta.getName());
                    statement.setString(2, fileMeta.getPath());
                    statement.setString(3, fileMeta.getPath() + File.separator + fileMeta.getName() + File.separator + "%");
                }

                // 执行 sql 删除操作
                statement.executeUpdate();
                System.out.println("删除成功 : " + fileMeta.getPath() + File.separator + fileMeta.getName());
                statement.close(); // 上述中代码理由多个 Statement 对象因此需要关闭多次, 因为 sql 有多个不同的模板情况
            }

            // 事务执行成功主动 commit
            connection.commit();
        } catch (SQLException e) {
            if (connection != null)
                connection.rollback();
        } finally {
            DbUtil.close(connection, null, null);
        }
    }
}
